package com.cordys.opensource.crom.sql;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.cordys.cpc.bsf.busobject.BusObject;
import com.cordys.cpc.bsf.classinfo.AttributeInfo;
import com.cordys.cpc.bsf.classinfo.ClassInfo;
import com.cordys.cpc.bsf.classinfo.RelationInfo_FK;
import com.cordys.opensource.crom.sql.Join.JoinType;

/**
 * This class represents a Table that takes part in a Query.
 * This table will typically become part of the from clause. Any columns that need to be selected,
 * have to be selected through the table object.
 * The table can be aliased.
 *
 */
public class Table
{
	private final String sAliasName;
	private final Class<? extends BusObject> persistenceClass;
	private final Map<String, Column> columns = new HashMap<String, Column>();
	private final List<Join> joinParts = new ArrayList<Join>();
	private final Query query;
	boolean hasBeenJoined = false;
	
	/**
	 * Creates a new table on top of the persistence class. This persistence class
	 * must come from a WS-AppServer db-generated package.
	 * @param persistenceClass
	 */
    public Table(Query query, Class<? extends BusObject> persistenceClass, String aliasName)
    {
    	this.persistenceClass = persistenceClass;
    	this.query = query;
    	this.sAliasName = aliasName;
    }
    
    /**
     * Returns the WS-AppServer database class on which this Table is based.
     * @return
     */
    public Class<? extends BusObject> getPersistenceClass()
    {
    	return persistenceClass;
    }

    /**
     * Returns the name of the table.
     * @return
     */
	public String getTableName()
	{
		return BusObject._getClassInfo(persistenceClass).getTableName();
	}

	/**
	 * Returns the alias with which the table participates in the query. This makes the table unique.
	 * @return
	 */
	public String getAliasName()
	{
		return (sAliasName!="") ? sAliasName : getTableName();
	}
    
    /**
     * Adds all columns of the table to the select clause.
     * @return
     */
    public Table selectAllColumns()
    {
    	ClassInfo oClassInfo = BusObject._getClassInfo(persistenceClass);
		if (oClassInfo != null)
		{
			AttributeInfo[] attrs = oClassInfo.getAttributeInfos2();
			for (AttributeInfo attr : attrs)
			{
				select(attr.getColumnName(), attr.getName());
			}
		}
    	return this;
    }
    
    /**
     * Deselects all columns of the table.
     */
    public void deselectColumns()
    {
    	for (Column column : this.columns.values())
    	{
			column.deselect();
		}
    }
    
    /**
     * A column can be part of the SELECT clause, or only participate in e.g. a WHERE clause.
     * This method returns the column without adding it to the SELECT clause. If it is already part 
     * of the SELECT clause, it is not removed from the SELECT clause.
     * Internally invokes getColumn(columnName, columnName);
     * @param columnName The name of the column to be added to the query.
     * @return
     */
    public Column getColumn(String columnName)
    {
    	return getColumn(columnName, columnName);
    }
    
    /**
     * A column can be part of the SELECT clause, or only participate in e.g. a WHERE clause.
     * This method returns the column without adding it to the SELECT clause. If it is already part 
     * of the SELECT clause, it is not removed from the SELECT clause.
     * @param columnName The name of the column to be added to the query.
     * @param aliasName The alias name for the column. This will make the column unique in the query.
     * @return
     */
    public Column getColumn(String columnName, String aliasName)
    {
    	Column column = columns.get(aliasName);
    	if (column==null)
    	{
    		column = new Column(query, this, columnName, aliasName, false);
    		columns.put(aliasName, column);
    	}
    	return column;
    }
    
    /**
     * This method returns the column and adds it to the SELECT clause. 
     * @param columnName The name of the column to be added to the query.
     * @param aliasName The alias name for the column. This will make the column unique in the query.
     * @return
     */
    public Column select(String columnName, String aliasName)
    {
    	Column column = getColumn(columnName, aliasName);
    	column.select();
    	return column;
    }
    
    /**
     * This method returns the column and adds it to the SELECT clause. 
     * Internally invokes select(columnName, columnName);
     * @param columnName The name of the column to be added to the query.
     * @return
     */
    public Column select(String columnName)
    {
    	return select(columnName, columnName);
    }
    
    /**
     * Returns true if this table has been joined with some other table.
     * In that case, the other table would take care of printing the from clause for this table.
     * @return
     */
    boolean hasBeenJoined()
    {
    	return this.hasBeenJoined;
    }
    
    /**
     * Join the other table with this one, through the specified type.
     * @param type
     * @param table
     * @return
     */
	private Join join(JoinType type, Table table)
	{
		Join newJoin = new Join(type, this, table);
		table.hasBeenJoined = true;
		joinParts.add(newJoin);
		return newJoin;
	}
	
    /**
     * Join the other table with this one, through the specified type.
     * @param type
     * @param table
     * @return
     */
	private Join join(JoinType type, Table table, RelationInfo_FK foreignKeyRelation)
	{
		Join newJoin = new Join(type, this, table, foreignKeyRelation);
		table.hasBeenJoined = true;
		joinParts.add(newJoin);
		return newJoin;
	}
	
	/**
	 * Join the other table with this one.
	 * @param table
	 * @return
	 */
	@Deprecated
	public Join innerJoin(Table table)
	{
		return join(JoinType.Inner, table);
	}
	
	/**
	 * Join the other table with this one on the foreign key relation
	 * @param table
	 * @return
	 */
	public Join innerJoin(Table table, RelationInfo_FK foreignKeyRelation)
	{
		return join(JoinType.Inner, table, foreignKeyRelation);
	}

	/**
	 * Left join the other table with this one.
	 * @param table
	 * @return
	 */
	@Deprecated
	public Join leftOuterJoin(Table table)
	{
		return join(JoinType.LeftOuter, table);
	}
	
	/**
	 * Left join the other table with this one on the foreign key relation
	 * @param table
	 * @return
	 */
	public Join leftOuterJoin(Table table, RelationInfo_FK foreignKeyRelation)
	{
		return join(JoinType.LeftOuter, table, foreignKeyRelation);
	}

	/**
	 * Right join the other table with this one.
	 * @param table
	 * @return
	 */
	@Deprecated
	public Join rightOuterJoin(Table table)
	{
		return join(JoinType.RightOuter, table);
	}

	/**
	 * Right join the other table with this one on the foreign key relation.
	 * @param table
	 * @return
	 */
	public Join rightOuterJoin(Table table, RelationInfo_FK foreignKeyRelation)
	{
		return join(JoinType.RightOuter, table, foreignKeyRelation);
	}

	/**
	 * Full join with the other table.
	 * @param table
	 * @return
	 */
	@Deprecated
	public Join fullJoin(Table table)
	{
		return join(JoinType.Full, table);
	}

	/**
	 * Full join with the other table on the foreign key relation.
	 * @param table
	 * @return
	 */
	public Join fullJoin(Table table, RelationInfo_FK foreignKeyRelation)
	{
		return join(JoinType.Full, table, foreignKeyRelation);
	}
    
	/**
	 * Returns a string representation of all columns in this table that need 
	 * to become part of the SELECT clause.
	 * @return
	 */
    public String getSelectString()
    {
    	boolean isFirst = true;
    	StringBuilder selectString = new StringBuilder();
    	for (Column column : columns.values())
    	{
    		if (column.isSelected())
    		{
    			if (!isFirst) selectString.append(",");
    			selectString.append(" "+column.getUnaliasedString());
    			isFirst = false;
    		}
		}
    	return selectString.toString();
    }
    
    /**
     * Return a string representation of this table along with it's joins.
     */
    public String toString()
    {
		String aliasName = getAliasName();
		String tableName = getTableName();
		
		final StringBuilder sb = new StringBuilder(tableName);
		if (!tableName.equals(aliasName))
		{
			sb.append(" "+aliasName);
		}
		
		if (joinParts.size() > 0)
		{
			for (Join join : joinParts)
			{
				sb.append("\n " + join.toString());
			}
		}

    	return sb.toString();
    }
	
	/**
	 * Compare two tables whether they have the same alias name.
	 */
	public boolean equals(Object that)
	{
		if (this==that) return true;
		if (that==null) return false;
		if (! (that instanceof Table)) return false;
    	return this.getAliasName().equals(((Table)that).getAliasName());
	}
}
